﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CommonLib {
    public class Tracer {
        private Screen screen;
        private Scene scene;

        private Ray _ray;
        private Vector3 _color, _lightDir, _primNormal, _node, _tmp;
        public Tracer(Screen scr, Scene sce) {
            this.screen = scr;
            this.scene = sce;

            this._ray = new Ray(new Vector3(), new Vector3(1, 0, 0));
            this._color = new Vector3();
            this._lightDir = new Vector3();
            this._primNormal = new Vector3();
            this._node = new Vector3();
            this._tmp = new Vector3();
        }
        public Screen Screen {
            get { return this.screen; }
        }
        public Primitive findNearest(Ray ray, ref double distance) {
            Primitive nearest = null;

            for (int j = 0; j < scene.CountPrimitive(); j++) {
                Primitive tmp = scene.GetPrimitive(j);
                if (tmp.isIntersect(ray, ref distance) == true)
                    nearest = tmp;
            }
            return nearest;
        }
        public void seasonDiffuse(Primitive nrst, Vector3 bndry, Vector3 cffcnt) {
            for (int j = 0; j < scene.CountPrimitive(); j++) {
                Primitive light = scene.GetPrimitive(j);
                if (light.isLight() == false)
                    continue;
                light.assignNormal(bndry, _lightDir);
                nrst.assignNormal(bndry, _primNormal);
                double arg = -_primNormal.dot(_lightDir);
                if ((0 < arg) == false)
                    continue;
                cffcnt.mac(nrst.Material.Diffuse * arg, light.Material.Color);
            }
        }
        public Primitive raytrace(Ray ray, Vector3 color, Vector3 node) {
            Primitive nearest = null;
            double distance = Double.MaxValue;

            nearest = findNearest(ray, ref distance);

            if (nearest == null)
                return nearest;
            if (nearest.isLight()) {
                color.set(nearest.Material.Color);
                return nearest;
            }

            node.set(ray.Dir).mul(distance).add(ray.Eye);
            seasonDiffuse(nearest, node, color);
            color.mul(nearest.Material.Color);
            return nearest;
        }
        public void render() {
            for (int i = 0; i < screen.getSize(); i++) {
                Primitive prim;

                screen.assignRay(i, _ray);
                prim = raytrace(_ray, _color.set(0), _node);
                if (prim != null) {
                    prim.assignNormal(_node, _primNormal);
                    _ray.Dir.mac(-2.0 * _tmp.set(_ray.Dir).dot(_primNormal), _primNormal);
                    _ray.Eye.set(_node).mac(DoubleTester.Epsilon, _ray.Dir);
                    raytrace(_ray, _tmp.set(0), _node);
                    _tmp.mul(prim.Material.Color);
                    _color.mac(prim.Material.Reflection, _tmp);
                }
                screen.setPixel(i, _color);
            }
        }
    }
}
